package org.java.order.service.impl;
import io.seata.spring.annotation.GlobalTransactional;
import jdk.nashorn.internal.runtime.options.Options;
import lombok.extern.slf4j.Slf4j;
import org.java.auth.pojo.UserInfo;
import org.java.common.entity.Cart;
import org.java.common.enums.ShoppingEnum;
import org.java.common.exception.ShoppingException;
import org.java.common.util.JsonUtils;
import org.java.item.entity.Sku;
import org.java.order.conf.RabbitMqConfig;
import org.java.order.dao.OrderMapper;
import org.java.order.dao.UserOrderDetailMapper;
import org.java.order.entity.Order;
import org.java.order.enums.OrderEnum;
import org.java.order.exception.OrderException;
import org.java.order.feigns.SkuClient;
import org.java.order.interceptor.OrderInterceptor;
import org.java.order.service.OrderService;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

/**
 * @author WHQ
 * @version 1.0
 * @date 2022-3-15 0015 下午 3:56
 */
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
    //这是提交订单时存放到redis中的临时前缀
    private String submitOrder_prefix="goShopping:submitOrder:uid:";
    //这是生成订单的前缀
    private String order_prefix="goShopping:order:uid:";
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private OrderMapper orderMapper;
    @Autowired
    private SkuClient skuClient;
    @Resource
    private UserOrderDetailMapper userOrderDetailMapper;
    @Autowired
    private Order order;
    @Autowired
    private RabbitTemplate rabbitTemplate;

    private static final Logger LOGGER=LoggerFactory.getLogger(OrderServiceImpl.class);
    @Override
    public void createOrder() {
    }
    @Override
    public void submit(Cart[] carts) {
        //从拦截器中获取载荷对象
        UserInfo userInfo = OrderInterceptor.getUserInfo();
        //拼接key
        String key=submitOrder_prefix+userInfo.getId().toString();
        stringRedisTemplate.delete(key);
        //开始存放到redis中
        BoundHashOperations<String, Object, Object> ops = stringRedisTemplate.boundHashOps(key);
        for (Cart cart : carts) {
            ops.put(cart.getSkuId().toString(), JsonUtils.serialize(cart));
        }
        System.out.println("存放到reids中成功");
        stringRedisTemplate.expire(key,30,TimeUnit.MINUTES);
    }

    @Override
    public List<Cart> confirmOrder() {
        //从载荷中获取用户信息
        Long id = OrderInterceptor.getUserInfo().getId();
        //拼接key
        String key=submitOrder_prefix+id;
        if (!stringRedisTemplate.hasKey(key)){
            throw new OrderException(OrderEnum.ORDER_PAY_ERROR);
        }
        //从redis中取出数据
        BoundHashOperations<String, Object, Object> ops = stringRedisTemplate.boundHashOps(key);
        //得到所有的数据
        List<Object> values = ops.values();
        //利用jdk1.8新特性的stream，将Object转换需要的类型
        List<Cart> list = values.stream().map(k -> JsonUtils.parse(k.toString(), Cart.class)).collect(Collectors.toList());
        return list;
    }
    @Autowired
    private Redisson redisson;
    @GlobalTransactional(name = "seata-server",rollbackFor = Exception.class)
    @Override
    public void generateOrder(String tradeNum, Cart[] carts) {
        //得到载荷对象，获得id
        Long userId = OrderInterceptor.getUserInfo().getId();
        //拼成确认订单页面存放在redis中的key
        String key=submitOrder_prefix+userId;
        //拼接分布式锁的key
//        String lockKey=submitOrder_prefix+userId+tradeNum;
//        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, tradeNum);
//        if (!flag) {
//            throw new OrderException(OrderEnum.ORDER_UPDATE_ERROR);
//        }
        //先判断该key是否存在，防止重复提交
        Boolean aBoolean = stringRedisTemplate.hasKey(key);
        if (!aBoolean){
            System.out.println("重复提交!");
            throw new OrderException(OrderEnum.ORDER_PAY_ERROR);
        }
        //定义存储skuId的集合
        String[] skuList=new String[carts.length];
        //定义一个变量，计算总金额
        Long money=0L;
        //从载荷中得到用户id
        Long id = OrderInterceptor.getUserInfo().getId();
        //拼接key
//        String key=order_prefix+id;
        //得到redis中的信息
//        BoundHashOperations<String, Object, Object> ops = stringRedisTemplate.boundHashOps(key);
        //得到redisson锁对象
        //首先判断库存是否足够
        try {
            for (int i=0;i<carts.length;i++) {
                Cart cart=carts[i];
                //用锁锁住商品id
                RLock lock = redisson.getLock(cart.getSkuId().toString());
                lock.lock();
                //将cart.skuId存入skuIds
                skuList[i]=cart.getSkuId().toString();
                Sku sku = skuClient.findById(cart.getSkuId());
                if (cart.getNum()<sku.getInventory()){
                    //计算总金额
                    money+=(sku.getPrice()*cart.getNum())/100;
                    //本来应该操作库存服务，由于时间紧张，库存数据表并未建立，所以这里还是操作sku表
                    //1.库存足够，减少库存
                    skuClient.cutInventory(sku.getId(),cart.getNum());
                    System.out.println("》》》》》》》》》》》》》》》》》减少库存结束");
                    //2.调用userOrderDetailMapper,添加订单中商品的详细id(一个订单对应多个商品)
                    userOrderDetailMapper.insertOrderDetail(tradeNum,cart.getSkuId(),cart.getNum());
                    System.out.println(">>>>>>>>>>>>>>>>>>>>添加到订单详情表成功");
                }else {
                    throw new OrderException(OrderEnum.INVENTORY_INSUFFICIENT);
                }
                lock.unlock();

            }
        } catch (OrderException orderException) {
            orderException.printStackTrace();
            throw orderException;
        }
//3.调用orderMapper的创建订单的方法
        //封装order对象
        order.setOrderId(tradeNum);
        order.setOrderUserName(id.toString());
        order.setOrderAddrId(1);
        order.setOrderMoney(money);
        order.setOrderState(false);
        order.setOrderCreateTime(new Date());
        order.setOrderTimeoutState(true);
        orderMapper.createOrder(order);
//        int i=10/0;
        System.out.println("》》》》》》》》》》》》》》》》》》》创建订单成功");
        System.out.println("数据库操作成功>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        //对skuId数组进行序列化
        String skuListJson = JsonUtils.serialize(skuList);
        //创建map，封装消息
        Map delCartMap=new HashMap(1000);
        delCartMap.put("skuList",skuListJson);
        delCartMap.put("userId",userId);
        String json = JsonUtils.serialize(delCartMap);
        //订单编号以及数量都在carts对象中
        String cartsJson = JsonUtils.serialize(carts);
        //封装信息
        Map ttlMap = new HashMap(1000);
        ttlMap.put("carts",cartsJson);
        ttlMap.put("tradeNum",tradeNum);
        //序列化操作
        String ttlMapJson = JsonUtils.serialize(ttlMap);
        System.out.println("发送消息，对购物车中的已生成订单的sku进行删除");
        rabbitTemplate.convertAndSend(RabbitMqConfig.CART_EXCHANGE,"shopping.cart.del",json);
        System.out.println(">>>>>>>>><<<<<<<<<<<<<<<对购物车的删除消息发送成功!");
        System.out.println(">>>>>>>>>>>>>>>>>>>开始向ttl队列发送消息:消息内容为订单号，以及每件商品的编号，数量");
//        rabbitTemplate.convertAndSend(RabbitMqConfig.TTL_EXCHANGE,"order.ttl.carts",ttlMapJson);
        /**
         * 使用插件发送消息的时候会出现no_route的错误
         * 需要设置强制执行
         * 往延时队列发消息前设置Mandatory=false
         */
        rabbitTemplate.setMandatory(false);
        //通过插件发送消息
        rabbitTemplate.convertAndSend(RabbitMqConfig.DELAYED_EXCHANGE_NAME,
                "delayed.order",
                ttlMapJson,
                //可以携带一些参数，比如说消息的过期时间，不用在创建队列的时候指定，可以在发送的时候指定
                CorrelationData->{
            //设置过期时间
            CorrelationData.getMessageProperties().setDelay(60000);
            return CorrelationData;
        });
        System.out.println(">>>>>>>>>>>>>>>>>>向ttl队列中发送消息成功");
        //删除指定的key
        stringRedisTemplate.delete(key);
    }

    @Override
    public void modifyOrder(String tradeNum) {
        int rows=orderMapper.modifyOrder(tradeNum);
        if (rows>0){
            System.out.println("修改成功");
        }else {
            throw new OrderException(OrderEnum.ORDER_UPDATE_ERROR);
        }

    }

    @PostConstruct
    public void init(){
//        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
//            @Override
//            public void confirm(CorrelationData correlationData, boolean b, String s) {
//
//            }
//        });
        /**
         * PostConstruct:注解标识在构造函数运行之后加载
         * 消息发送到交换器Exchange后触发回调。
         *使用该功能需要开启确认，spring-boot中配置如下：
         *publisher-confirm-type: correlated
         */
        //上面匿名内部类的简写
        rabbitTemplate.setConfirmCallback((correlationData,flag,msg)->{
            if (flag){
                System.out.println("消息发送成功:"+correlationData.getId());
            }else {
                System.out.println("消息发送失败:"+correlationData.getId());
            }
        });

        /**
         * 通过实现ReturnCallback接口，
         * 如果消息从交换器发送到对应队列失败时触发
         * 比如根据发送消息时指定的routingKey找不到队列时会触发
         * 使用该功能需要开启确认，spring-boot中配置如下：
         * spring.rabbitmq.publisher-returns = true
         */
//        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
//            @Override
//            public void returnedMessage(Message message, int i, String s, String s1, String s2) {
//
//            }
//        });
        rabbitTemplate.setReturnCallback((message,replyCode,replyText,exchange,routingKey)->{
            LOGGER.error("消息被退回:{}", message);
            LOGGER.error("消息使用的交换机:{}", exchange);
            LOGGER.error("消息使用的路由键:{}", routingKey);
            LOGGER.error("描述:{}", replyText);
        });
    }
}
